package com.bolt.support.spring.jpa.query;

import com.bolt.support.sqlconfig.base.SqlStatement;
import com.bolt.support.sqlconfig.base.SqlText;
import com.bolt.support.sqlconfig.builder.SqlTemplateFactory;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.query.*;
import org.springframework.util.Assert;

import javax.persistence.EntityManager;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by Administrator on 2017/8/12.
 */
public class NativeConfigQuery implements RepositoryQuery {

    private final JdbcQueryMethod method;
    private final EntityManager entityManager;
    private final SqlTemplateFactory sqlTemplateFactory;

    private final Parameters<?, ?> parameters;

    public NativeConfigQuery(EntityManager em, JdbcQueryMethod method, SqlTemplateFactory sqlTemplateFactory) {
        Assert.notNull(em, "EntityManager must not be null!");
        this.entityManager = em;
        this.method = method;
        this.sqlTemplateFactory = sqlTemplateFactory;
        this.parameters = this.method.getParameters();

    }

    protected EntityManager getEntityManager() {
        return entityManager;
    }


    private SqlText getSqlText(String sqlId, Object parameterObject) {
        SqlStatement sqlStatement = getSqlStatement(sqlId);
        final SqlText sql = sqlStatement.getSqlText(parameterObject);
        return sql;
    }

    private SqlStatement getSqlStatement(String sqlId) {
        return sqlTemplateFactory.find(sqlId);
    }

    @Override
    public Object execute(Object[] values) {
        ParametersParameterAccessor accessor = new ParametersParameterAccessor(parameters, values);
        SqlText sqlText = createSqlText(values, accessor);
        Class<?> returnType = method.getReturnedObjectType();
        if (null == returnType && null != sqlText.getEntity()) {
            throw new IllegalStateException("return type not match sql config entity class");
        }
        if (null != sqlText.getEntity() && !returnType.equals(sqlText.getEntity())) {
            throw new IllegalStateException("return type not match sql config entity class");
        }
        JdbcQueryExecution execution = getExecution(entityManager, method, accessor);
        return execution.execute(sqlText);

    }

    public JdbcQueryExecution getExecution(EntityManager em, JdbcQueryMethod method, ParametersParameterAccessor accessor) {
        if (method.isCollectionQuery()) {
            return new CollectionExecution(em, method, accessor, this.sqlTemplateFactory.getConfiguration());
        } else if (method.isPageQuery()) {
            return new PagedExecution(em, method, accessor, this.sqlTemplateFactory.getConfiguration());
        } else {
            return new SingleObjectExecution(em, method, accessor, this.sqlTemplateFactory.getConfiguration());
        }
    }

    private SqlText createSqlText(Object[] values, ParametersParameterAccessor accessor) {
        Object parameterValue = doBindParameter(values, accessor);
        String sqlId = this.method.getAnnotatedSqlId();
        SqlText sqlText = getSqlText(sqlId, parameterValue);
        return sqlText;
    }

    private Object doBindParameter(Object[] values, ParametersParameterAccessor accessor) {
        int parameterIndex = 0;
        Map<String, Object> mapParameterValue = new HashMap<>();
        for (Parameter parameter : parameters) {
            if (parameter.isBindable()) {
                Object value = accessor.getBindableValue(parameterIndex);
                if (value instanceof Map) {
                    mapParameterValue.putAll((Map) value);
                } else {
                    mapParameterValue.put(parameter.getName().get(), value);
                }
                parameterIndex++;
            }
        }
        Object parameterValue = null;
        if (mapParameterValue.isEmpty()) {
            for (Object value : values) {
                if (value instanceof Pageable || value instanceof Sort) {
                    continue;
                }
                parameterValue = value;
            }
        }
        return parameterValue == null ? mapParameterValue : parameterValue;
    }

    @Override
    public QueryMethod getQueryMethod() {
        return method;
    }
}
